<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

</body>
<script>
    // 【1】基础用法
    let message = `Hello World`;
    console.log(message); //Hello World
    // 如果你碰巧要在字符串中使用反撇号，你可以使用反斜杠转义：
    let message0 = `Hello \` World`;
    console.log(message0); //Hello ` World
    //[1-1]值得一提的是，在模板字符串中，空格、缩进、换行都会被保留：
    let message1 = `
    <ul>
    <li>1</li>
    <li>2</li>
    </ul>
    `;
    console.log(message1);
    // 打印结果如下
    //  换行
    //      空格<ul>
    //      空格<li>1</li>
    //      空格<li>2</li>
    //      空格</ul>
    //  换行
    //[1-2]注意，打印的结果中第一行是一个换行，你可以使用 trim 函数消除换行：
    // 【关键词】消除首尾换行{而在标准api中，定义为去除首尾空格}
    let message2 = `
    <ul>
		<li>1</li>
		<li>2</li>
	</ul>
    `.trim();
    console.log(message2);
    // 打印结果如下
    //  换行被去掉
    // <ul>
    // 	<li>1</li>
    // 	<li>2</li>
    // </ul> 
    //  换行被去掉
    // 【2】嵌入变量
    // 模板字符串支持嵌入变量，只需要将变量名写在 ${} 之中，其实不止变量，任意的 JavaScript 表达式都是可以的：
    let x = 1,
        y = 2;
    let message3 = `<ul><li>${x}</li><li>${x + y}</li></ul>`;
    console.log(message3); // <ul><li>1</li><li>3</li></ul>
    // 值得一提的是，模板字符串支持嵌套:
    let arr4 = [{
        value: 1
    }, {
        value: 2
    }, , {
        value: 11
    }];
    let message4 = `
	<ul>
		${arr4.map((item) => {
        return `<li>${item.value}</li>`
    })}
	</ul>
`;
    console.log(message4, '');//打印结果如下
    let arr44 = [{
        value: '索尼'
    }, {
        value: '鳌拜'
    }, , {
        value: '雍正'
    }];
    let message44 = "";
    //  `${arr44.forEach((item) => {
    //     message44 += `'${item.value}',`
    // 	})}`;
    arr44.forEach((item) => {
        message44 += `'${item.value}',`
    });
    console.log(message44, '安徽啊');//打印结果如下
    var hello = 'good good day, day day good'
    var world = `please look "${hello}", cool style, please enjoy it`
    console.info(world)

    // <ul>
    // 	<li>1</li>,<li>2</li>,,<li>11</li>
    // </ul>

    // 注意，在 li 标签中间多了一个逗号，这是因为当大括号中的值不是字符串时，会将其转为字符串，
    // 比如一个数组 [1, 2, 3] 就会被转为 1,2,3，逗号就是这样产生的。
    // 如果你要消除这个逗号，你可以先 join 一下：
    let arr5 = [{ value: 1 }, { value: 2 }];
    let message5 = `
        <ul>
            ${arr5.map((item) => {
        return `
                    <li>${item.value}</li>
                `
    }).join('')}
        </ul>
    `;
    console.log(message5);// 打印结果如下：
    // <ul>

    //         <li>1</li>

    //         <li>2</li>

    // </ul>
    // 打印结果如下：
    // 【2-1】
    let arr6 = ['<li>1</li>', '<li>2</li>'];
    // 注意此处有个知识点，就是数组中的每一项，会被直接全部展开放入
    let message6 = `
        <ul>
            ${arr6}
        </ul>
    `;
    console.log(message6);
    // 【3】标签模板
    // 模板标签是一个非常重要的能力，模板字符串可以紧跟在一个函数名后面，该函数将被调用来处理这个模板字符串，举个例子：

    let x3 = 'Hi', y3 = 'Kevin', z3 = 'end';
    var res3 = message32`${x3}, I am ${y3},and ${z3}`;
    console.log(res3);
    // 我们可以自定义 message 函数来处理返回的字符串:

    // literals：英文是字面量的意思
    // 注意在这个例子中 literals 的第一个元素和最后一个元素都是空字符串
    function message31(literals, value1, value2, value3) {
        debugger
        console.log(literals); // [ "", ", I am ", ",and ","" ]
        console.log(value1); // Hi
        console.log(value2); // Kevin
        console.log(value3); // Kevin
    }
    // 我们利用这些参数将其拼合回去：
    function message32(literals, ...values) {
        debugger
        let result = '';
        for (let i = 0; i < values.length; i++) {
            result += literals[i];
            result += values[i];
        }
        result += literals[literals.length - 1];
        return result;
    }
    // 你也可以这样写：
    function message33(literals, ...values) {
        // debugger
        let result = literals.reduce((prev, next, i) => {
            // 这里其实非常简单，仅仅是普通的reduce遍历，多加入了一个values的索引读取而已
            let value = values[i - 1];
            return prev + value + next;
        });
        return result;
    }
    // 学着拼合回去是一件非常重要的事情，因为我们经过各种处理，最终都还是要拼回去的……
    // 【4】oneLine【同一行的意思】
    // 讲完了基础，我们可以来看一些实际的需求：
    let message41 = `
        Hi,
        Daisy!
        I am
        Kevin.
    `;
    // 出于可读性或者其他原因，我希望书写的时候是换行的，但是最终输出的字符是在一行，这就需要
    // 借助模板标签来实现了，我们尝试写一个这样的函数：
    // oneLine 第一版
    function oneLine(template, ...expressions) {
        debugger
        let result = template.reduce((prev, next, i) => {
            debugger
            let expression = expressions[i - 1];
            return prev + expression + next;
        });
        result = result.replace(/(\s+)/g, " ");
        result = result.trim();
        return result;
    }
    // 实现原理很简单，拼合回去然后将多个空白符如换行符、空格等替换成一个空格。
    // 使用如下：
    let message42 = oneLine`
        Hi,
        Daisy!
        I am
        Kevin.
    `;
    console.log(message42); // Hi, Daisy! I am Kevin.
    // 不过你再用下去就会发现一个问题，如果字符间就包括多个空格呢？举个例子：
    let message43 = oneLine`
    Preserve eg sentences.  Double
    spaces within input lines.
    `;
    // 如果使用这种匹配方式，sentences. 与 Double 之间的两个空格也会被替换成一个空格。

    // 我们可以再优化一下，我们想要的效果是将每行前面的多个空格替换成一个空格，
    // 其实应该匹配的是换行符以及换行符后面的多个空格，然后将其替换成一个空格，我们可以将正则改成：

    // result = result.replace(/(\n\s*)/g, " ");

    // 就可以正确的匹配代码。最终的代码如下：

    // oneLine 第二版
    function oneLine(template, ...expressions) {
        let result = template.reduce((prev, next, i) => {
            let expression = expressions[i - 1];
            return prev + expression + next;
        });
        result = result.replace(/(\n\s*)/g, " ");
        result = result.trim();
        return result;
    }
    // 【5】stripIndents【Indents缩进，strip剥离，除去】
    // 假设有这样一段 HTML：
    let html = `
        <span>1<span>
        <span>2<span>
            <span>3<span>
    `;
    // 为了保持可读性，我希望最终输入的样式为：
    // <span>1<span>
    // <span>2<span>
    // <span>3<span>
    // 其实就是匹配每行前面的空格，然后将其替换为空字符串。

    // stripIndents 第一版
    function stripIndents(template, ...expressions) {
        let result = template.reduce((prev, next, i) => {
            let expression = expressions[i - 1];
            return prev + expression + next;
        });
        result = result.replace(/\n[^\S\n]*/g, '\n');
        result = result.trim();
        return result;
    }
    // 最难的或许就是这个正则表达式了：

    // result = result.replace(/\n[^\S\n]*/g, '\n');

    // \S 表示匹配一个非空白字符

    // [^\S\n] 表示匹配非空白字符和换行符之外的字符，其实也就是空白字符去除换行符

    // \n[^\S\n]* 表示匹配换行符以及换行符后的多个不包含换行符的空白字符

    // replace(/\n[^\S\n]*/g, '\n') 表示将一个换行符以及换行符后的多个不包含
    // 换行符的空白字符替换成一个换行符，其实也就是将换行符后面的空白字符消掉的意思

    // 其实吧，不用写的这么麻烦，我们还可以这样写：

    // result = result.replace(/^[^\S\n]+/gm, '');

    // 看似简单了一点，之所以能这样写，是因为匹配模式的缘故，你会发现，这次除了匹配全局之外，
    // 这次我们还匹配了多行，m 标志用于指定多行输入字符串时应该被视为多个行，而且如果
    // 使用 m 标志，^ 和 $ 匹配的开始或结束是输入字符串中的每一行，而不是整个字符串的开始或结束。

    // [^\S\n] 表示匹配空白字符去除换行符

    // ^[^\S\n]+ 表示匹配以去除换行符的空白字符为开头的一个或者多个字符

    // result.replace(/^[^\S\n]+/gm, '') 表示将每行开头一个或多个去除换行符的空白字符
    // 替换成空字符串，也同样达到了目的。

    // 最终的代码如下：

    // stripIndents 第二版
    function stripIndents(template, ...expressions) {
        let result = template.reduce((prev, next, i) => {
            let expression = expressions[i - 1];
            return prev + expression + next;
        });


        result = result.replace(/^[^\S\n]+/gm, '');
        result = result.trim();

        return result;
    }
    // 【6】stripIndent
    // 注意，这次的 stripIndent 相比上面一节的标题少了一个字母 s，而我们想要实现的功能是：

    let html6 = `
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        <ul>
    `;


    // 其实也就是去除第一行的换行以及每一行的部分缩进。

    // 这个实现就稍微麻烦了一点，因为我们要计算出每一行到底要去除多少个空白字符。

    // 实现的思路如下：

    // 使用 match 函数，匹配每一行的空白字符，得到一个包含每一行空白字符的数组
    // 数组遍历比较，得到最小的空白字符长度
    // 构建一个正则表达式，然后每一行都替换掉最小长度的空白字符
    // 实现的代码如下：

    let html61 = `
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        <ul>
    `;


    function stripIndent(template, ...expressions) {
        let result = template.reduce((prev, next, i) => {
            let expression = expressions[i - 1];
            return prev + expression + next;
        });

        const match = result.match(/^[^\S\n]*(?=\S)/gm);
        console.log(match); // Array [ "    ", "        ", "        ", "        ", "    " ]

        const indent = match && Math.min(...match.map(el => el.length));
        console.log(indent); // 4

        if (indent) {
            const regexp = new RegExp(`^.{${indent}}`, 'gm');
            console.log(regexp); // /^.{4}/gm

            result = result.replace(regexp, '');
        }

        result = result.trim();

        return result;
    }
    // 值得一提的是，我们一般会以为正则中 . 表示匹配任意字符，其实是匹配除换行符之外的任何单个字符。

    // 最终精简的代码如下：

    function stripIndent(template, ...expressions) {
        let result = template.reduce((prev, next, i) => {
            let expression = expressions[i - 1];
            return prev + expression + next;
        });

        const match = result.match(/^[^\S\n]*(?=\S)/gm);
        const indent = match && Math.min(...match.map(el => el.length));

        if (indent) {
            const regexp = new RegExp(`^.{${indent}}`, 'gm');
            result = result.replace(regexp, '');
        }

        result = result.trim();

        return result;
    };
    // 【7】includeArrays
    // 前面我们讲到为了避免 ${} 表达式中返回一个数组，自动转换会导致多个逗号的问题，需要每次都将数组最后
    // 再 join('') 一下，再看一遍例子：
    let arr = [{ value: 1 }, { value: 2 }];
    let message71 = `
        <ul>
            ${arr.map((item) => {
        return `
                    <li>${item.value}</li>
                `
    }).join('')}
        </ul>
    `;
    console.log(message);
    // 利用标签模板，我们可以轻松的解决这个问题：
    function includeArrays(template, ...expressions) {
        let result = template.reduce((prev, next, i) => {
            let expression = expressions[i - 1];
            if (Array.isArray(expression)) {
                expression = expression.join('');
            }
            return prev + expression + next;
        });
        result = result.trim();
        return result;
    }
</script>

</html>